/* Alice Robinson and Holly Beach
 * TCSS491 - Winter 2012*/

import java.awt.*;
import java.awt.event.*;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.nio.IntBuffer;
import java.util.ArrayList;
import java.util.Random;
import java.util.Scanner;

import javax.media.opengl.*;
import javax.media.opengl.awt.*;
import javax.media.opengl.glu.*;
import javax.swing.*;

import com.jogamp.common.nio.*;
import com.jogamp.opengl.util.*;

/**PWorldMain contains the main method for launching a Computational
 * World that includes physics. */
public class PWorldMain implements GLEventListener, ActionListener, MouseListener, MouseMotionListener {
	public static Peer peer;
	
	private static final int TARGET_FPS = 30;
	private static JFrame appFrame;
	//private static RenderArea my_display;
	
	private JButton my_circle_button = new JButton("Circle");
	private JButton my_triangle_button = new JButton("Triangle");
	private JButton my_square_button = new JButton("Square");
	private JButton my_house_button = new JButton("House");
	
	private JButton my_release_button = new JButton("Clear Selection");
	private JButton my_delete_button = new JButton("Delete Shape");
	private JButton my_up_button = new JButton("Up");
	private JButton my_down_button = new JButton("Down");
	private JButton my_left_button = new JButton("Left");
	private JButton my_right_button = new JButton("Right");

	private static SceneGraphNode sceneGraphRoot;
	private static boolean pickNextFrame;
	private static Point pickedPoint;
	private static double left, right, top, bottom;
	private static ArrayList<PhysicsObject> objects;
	
	private static Point orientationPoint1;
	private static Point orientationPoint2;
	private PhysicsObject my_picked_object;
	private double deltaY;
	private double deltaX;
	
	private boolean my_draggable_status = false;
	private String my_direction = " ";
	private String my_direction2 = " ";
	java.util.List<SceneGraphNode> the_picked;
	
	public static void main(String[] args) {
		establishP2PCommunication();
		
		GLProfile.initSingleton();
		SwingUtilities.invokeLater(new Runnable() {
			public void run() {
				(new PWorldMain()).createAndShowGUI();
			}
		});
		
		displayNetworkOptions();
	}

	private void createAndShowGUI() {
		// Fix for background flickering
		// System.setProperty("sun.awt.noerasebackground", "true");
		
		GLProfile profile = GLProfile.getDefault();
		GLCapabilities capabilities = new GLCapabilities(profile);
		GLCanvas canvas = new GLCanvas(capabilities);
		canvas.addGLEventListener(this);
		canvas.addMouseListener(this);
		canvas.addMouseMotionListener(this);
		
		//add physics objects/shapes
		objects = new ArrayList<PhysicsObject>();
		sceneGraphRoot = new SceneGraphNode(false);
		
		PhysicsObject obj; 
	
		//circles
		obj = new PCircle(.5f);
		obj.velocity.x = -.5f;
		obj.acceleration.y = -5f;
		attachObject(obj);
	
		//Triangle
		obj = new PTriangle(1f);
		obj.mass = 1.2f;
		obj.position.y = 1;
		obj.velocity.x = 3f;
		obj.acceleration.y = -.2f;
		attachObject(obj);
		
		//Square
		obj = new PSquare(1f);
		obj.mass = 1.3f;
		obj.position.y = -3f;
		obj.position.x = 3f;
		obj.velocity.x = .1f;
		obj.acceleration.y = 1f;
		attachObject(obj);
		
		//House
		obj = new PHouse(1);
		obj.mass = 1.8f;
		obj.position.y = 2f;
		obj.position.x = -2f;
		obj.velocity.x = -.1f;
		obj.velocity.y = -4f;
		attachObject(obj);
	
//add the boundary
	
		attachObject(new HalfSpace(new Vector2f(-5, 0), new Vector2f(1, 0), HalfSpace.LEFT_BOUNDARY));
		attachObject(new HalfSpace(new Vector2f(5, 0), new Vector2f(-1, 0), HalfSpace.RIGHT_BOUNDARY));
		attachObject(new HalfSpace(new Vector2f(0, 5), new Vector2f(0, -1), HalfSpace.TOP_BOUNDARY));
		attachObject(new HalfSpace(new Vector2f(0, -5), new Vector2f(0, 1), HalfSpace.BOTTOM_BOUNDARY));
		// Add independent SceneGraphNode for all the HalfSpaces.
		sceneGraphRoot.addChild(new SceneGraphNode(false) {
			public void renderGeometry(GLAutoDrawable drawable) {
				GL2 gl = drawable.getGL().getGL2();	
				gl.glColor3f(1, 1, 1);
				gl.glBegin(GL.GL_LINE_LOOP);
				gl.glVertex2f(-5, -5);
				gl.glVertex2f(5, -5);
				gl.glVertex2f(5, 5);
				gl.glVertex2f(-5, 5);
				gl.glEnd();
			}
		});
		
		
		//Title bar
		String[] my_titles = new String[10];
		my_titles[0] = "Now 20% cooler!";
		my_titles[1] = "The fun has been doubled!";
		my_titles[2] = "Playing with shapes in 10 seconds flat.";
		my_titles[3] = "Moonshine gravy.";
		my_titles[4] = "Now contains less arsenic!";
		my_titles[5] = "When life gives you lemons, give the lemons back. I don't want your lemons.";
		my_titles[6] = "Kilroy was here.";
		my_titles[7] = "Now with zero calories!";
		my_titles[8] = "Now you're thinking with java!";
		my_titles[9] = "Straight to the moon.";
		
		Random rn = new Random();
		int a_random = rn.nextInt(10);
		
		appFrame = new JFrame(my_titles[a_random]);
		Container the_content = appFrame.getContentPane();
		the_content.setBackground(Color.BLACK);
		the_content.setLayout(new GridBagLayout());
		GridBagConstraints gBC = new GridBagConstraints();
		gBC.fill = GridBagConstraints.BOTH;
		
		JPanel controlArea = new JPanel();
		
//		JLabel helpText = new JLabel("Hint 1: Click and drag to move objects         ");
//		controlArea.add(helpText);
		
		my_circle_button.addActionListener(this);
		controlArea.add(my_circle_button);
		my_triangle_button.addActionListener(this);
		controlArea.add(my_triangle_button);
		my_square_button.addActionListener(this);
		controlArea.add(my_square_button);
		my_house_button.addActionListener(this);
		controlArea.add(my_house_button);
		/*
		my_release_button.addActionListener(this);
		controlArea.add(my_release_button);
		my_delete_button.addActionListener(this);
		controlArea.add(my_delete_button);
		*/
		
//		JLabel helpText2 = new JLabel("Hint 2: A mouse works a lot better than a laptop touch pad.");
//		controlArea.add(helpText2);
		
		gBC.weighty = 0.5;
		gBC.weightx = 0.1;
		gBC.gridx = 0;
		gBC.gridy = 0;
		appFrame.add(controlArea, gBC);
		
		gBC.weighty = 50;
		gBC.weightx = 0.1;
		gBC.gridx = 0;
		gBC.gridy = 1;
		appFrame.add(canvas, gBC);
		
		KeyboardFocusManager manager = KeyboardFocusManager.getCurrentKeyboardFocusManager();
        manager.addKeyEventDispatcher(new MyDispatcher());
		
		appFrame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
		appFrame.setMinimumSize(new Dimension(256, 256));
		//appFrame.add(canvas, gBC);
		appFrame.pack();
		appFrame.setSize(512, 512);
//		if (Toolkit.getDefaultToolkit().isFrameStateSupported(JFrame.MAXIMIZED_BOTH))
//			appFrame.setExtendedState(appFrame.getExtendedState() | JFrame.MAXIMIZED_BOTH);
		appFrame.setVisible(true);
	}

	public static void attachObject(PhysicsObject object) {
		if (object.renderable != null)
			sceneGraphRoot.addChild(object.renderable);
		objects.add(object);
	}

	public static void detachObject(PhysicsObject object) {
		if (object.renderable != null)
			sceneGraphRoot.removeChild(object.renderable);
		int index = objects.indexOf(object);
		if (index == -1)
		//	return;
			throw new IllegalArgumentException();
		objects.set(index, objects.get(objects.size()-1));
		objects.remove(objects.size()-1);
	}
	public void display(GLAutoDrawable drawable) {
		GL2 gl = drawable.getGL().getGL2();
		gl.glClear(GL.GL_COLOR_BUFFER_BIT);
		if (pickNextFrame || (my_draggable_status == true && (my_direction != " " && my_direction2 != " ")) ) {
			GLU glu = GLU.createGLU(gl);
			int viewport[] = new int[4];
			gl.glGetIntegerv(GL.GL_VIEWPORT, viewport, 0);
			gl.glMatrixMode(GL2.GL_PROJECTION);
			gl.glPushMatrix();
			gl.glLoadIdentity();
			glu.gluPickMatrix(pickedPoint.x, (double)(viewport[3] - pickedPoint.y), 1, 1, viewport, 0);
			gl.glOrtho(left, right, bottom, top, -1, 1);
			gl.glMatrixMode(GL2.GL_MODELVIEW);
			//System.out.println(sceneGraphRoot.getPicked(drawable));
			
			
			
			if( sceneGraphRoot.getPicked(drawable).isEmpty() == false) {
				my_draggable_status = true;
//				System.out.println("Selected an object for moving.");
				the_picked = sceneGraphRoot.getPicked(drawable);
				
				
				for (int i = 0; i < objects.size(); i++) {
					if (objects.get(i).renderable == the_picked.get(0)) {
						my_picked_object = objects.get(i);
					}
				}
			}
			
			if (my_picked_object != null) {

				if (my_draggable_status && my_direction == "Up") {
					//System.out.println("Moved Up.");
					//my_direction = " ";
					the_picked.get(0).translateY += (.54 * deltaY);
					my_picked_object.renderable.translateY += (.5 * deltaY);
					my_picked_object.position.y += (.5 * deltaY);
				} else if (my_draggable_status && my_direction == "Down") {
					//System.out.println("Moved Down.");
					//my_direction = " ";
					the_picked.get(0).translateY -= (.5 * deltaY);
					my_picked_object.renderable.translateY -= (.5 * deltaY);
					my_picked_object.position.y -= (.5 * deltaY);
				}
				
				my_direction = " ";
				
				if (my_draggable_status && my_direction2 == "Right") {
					//System.out.println("Moved Right.");
					//my_direction2 = " ";
					the_picked.get(0).translateX += (.5 * deltaX);
					my_picked_object.renderable.translateX += (.5 * deltaX);
					my_picked_object.position.x += (.5 * deltaX);
				} else if (my_draggable_status && my_direction2 == "Left") {
					//System.out.println("Moved Left.");
					//my_direction2 = " ";
					the_picked.get(0).translateX -= (.5 * deltaX);
					my_picked_object.renderable.translateX -= (.5 * deltaX);
					my_picked_object.position.x -= (.5 * deltaX);
				}
				
				my_direction2 = " ";

			}
			
			gl.glMatrixMode(GL2.GL_PROJECTION);
			gl.glPopMatrix();
			gl.glMatrixMode(GL2.GL_MODELVIEW);
			pickNextFrame = false;
		}
		
		for (PhysicsObject object : objects) {
			object.updateState(1f / TARGET_FPS);
		}
		boolean noCollisions = false;
		for (int repeat = 0; repeat < 10 && !noCollisions; repeat++) {
			noCollisions = true;		
			for (int i = 0; i < objects.size(); i++) {
				PhysicsObject a = objects.get(i);
				for (int j = i + 1; j < objects.size(); j++) {
					PhysicsObject b = objects.get(j);
					CollisionInfo cInfo = a.getCollision(b);
					if (cInfo != null) {
						noCollisions = false;
						a.resolveCollision(b, cInfo);
					}
				}
			}
			if (!(repeat+1 < 10 && !noCollisions)) {
				//System.out.println(repeat + 1);
			}
		}
		for (PhysicsObject object : objects)
			object.updateRenderable();
		sceneGraphRoot.render(drawable);
	}

	public void dispose(GLAutoDrawable drawable) {
	}

	public void init(GLAutoDrawable drawable) {
		GL2 gl = drawable.getGL().getGL2();
		gl.glClearColor(0, 0, 0, 0);
		IntBuffer selectBuffer = Buffers.newDirectIntBuffer(3);
		gl.glSelectBuffer(selectBuffer.capacity(), selectBuffer);
		FPSAnimator fps = new FPSAnimator(drawable, 30);
		fps.start();
	}

	
	public void reshape(GLAutoDrawable drawable, int x, int y, int width, int height) {
		final double UNIT = 11;
		
		if (width < height) {
			left = -UNIT / 2;
			right = UNIT / 2;
			top = (double)height / width * UNIT / 2;
			bottom = -top;
		} else {
			top = UNIT / 2;
			bottom = -UNIT / 2;
			right = (double)width / height * UNIT / 2;
			left = -right;
		}	
		GL2 gl = drawable.getGL().getGL2();
		gl.glMatrixMode(GL2.GL_PROJECTION);
		gl.glLoadIdentity();
		gl.glOrtho(left, right, bottom, top, -1, 1);
		gl.glMatrixMode(GL2.GL_MODELVIEW);
	}

	
	@Override
	public void actionPerformed(ActionEvent the_action) {
		Object the_source = the_action.getSource();
		if(the_source == my_square_button) {
			PhysicsObject obj;
			obj = new PSquare(1f);
			obj.mass = 1.3f;
			obj.position.y = 0f;
			obj.position.x = 2f;
			obj.velocity.x = .1f;
			obj.acceleration.y = -4f;
			attachObject(obj);
		} else if(the_source == my_circle_button) {			
			PhysicsObject obj;
			obj = new PCircle();
			obj.velocity.x = .5f;
			obj.acceleration.y = 3f;
			attachObject(obj);
		} else if(the_source == my_triangle_button) {
			PhysicsObject obj;
			obj = new PTriangle(1);
			obj.mass = 1.2f;
			obj.position.y = 1;
			obj.velocity.x = 1f;
			obj.acceleration.y = -5f;
			attachObject(obj);
		} else if(the_source == my_house_button) { //house
			PhysicsObject obj;
			obj = new PHouse(1);
			obj.mass = 1.5f;
			obj.position.y = 2f;
			obj.position.x = -2f;
			obj.velocity.x = .1f;
			obj.velocity.y = -4f;
			attachObject(obj);
		} else if(the_source == my_up_button) {
			if(my_draggable_status == true) {
				my_direction = "Up";
			}
		} else if(the_source == my_down_button) {
			if(my_draggable_status == true) {
				my_direction = "Down";
			}
		} else if(the_source == my_left_button) {
			if(my_draggable_status == true) {
				my_direction2 = "Left";
			}
		} else if(the_source == my_right_button) {
			if(my_draggable_status == true) {
				my_direction2 = "Right";
			}
		} else if(the_source == my_release_button) {
			if(my_draggable_status == true) {
				my_draggable_status = false;
//				System.out.println("Let go of the object for moving.");
			}
		} else if (the_source == my_delete_button){
			if(my_draggable_status){ //only delete if it is "selected"
				//need to figure out how to remove the physics object
				//from the objects array.  Or do we need to since 
				//it's renderable will be null?
				sceneGraphRoot.removeChild(the_picked.get(0));
			}
		}
	}

	@Override
	public void mouseClicked(MouseEvent e) {
		
		
	}

	@Override
	public void mousePressed(MouseEvent e) {
		
		pickNextFrame = true;
		pickedPoint = new Point(e.getX(), e.getY());
		orientationPoint2 = pickedPoint;
		
	}

	@Override
	public void mouseReleased(MouseEvent e) {
		
		if(my_draggable_status == true) {
			my_draggable_status = false;
//			System.out.println("Let go of the object for moving.");
		}
		
	}

	@Override
	public void mouseEntered(MouseEvent e) {
		// TODO Auto-generated method stub
		
	}

	@Override
	public void mouseExited(MouseEvent e) {
		// TODO Auto-generated method stub
		
	}
	
	private class MyDispatcher implements KeyEventDispatcher {
        @Override
        public boolean dispatchKeyEvent(KeyEvent e) {
            if (e.getID() == KeyEvent.KEY_PRESSED) {
            	if (my_draggable_status == true) {
        			if( e.getKeyCode() == KeyEvent.VK_RIGHT ) {
        				my_direction2 = "Right";
        			} else if( e.getKeyCode() == KeyEvent.VK_LEFT ) {
        				my_direction2 = "Left";
        			} else if( e.getKeyCode() == KeyEvent.VK_UP ) {
        				my_direction = "Up";
        			} else if( e.getKeyCode() == KeyEvent.VK_DOWN ) {
        				my_direction = "Down";
        			}
        		}
            }
            
            return false;
        }
    }

	@Override
	public void mouseDragged(MouseEvent e) {
		
		orientationPoint1 = orientationPoint2;
		orientationPoint2 = e.getPoint();
		//System.out.println(orientationPoint1 + "" + orientationPoint2);
		
		double changeInX = orientationPoint2.getX() - orientationPoint1.getX();
		double changeInY = orientationPoint2.getY() - orientationPoint1.getY();
		
		if( my_draggable_status == true) {
			if( changeInX > -1) {
				my_direction2 = "Right";
			} else if ( changeInX < 0) {
				my_direction2 = "Left";
			}
			
			if( changeInY > -1) {
				my_direction = "Down";
			} else if ( changeInY < 0) {
				my_direction = "Up";
			}
		}
		
		changeInY = Math.abs(changeInY);
		changeInX = Math.abs(changeInX);
		
		if (changeInY > changeInX) {
			deltaY = 1;
			deltaX = changeInX/changeInY;
			//System.out.println("deltaY: " + deltaY);
			//System.out.println("deltaX: " + deltaX);
		} else if (changeInX > changeInY) {
			deltaX = 1;
			deltaY = changeInY/changeInX;
			//System.out.println("deltaY: " + deltaY);
			//System.out.println("deltaX: " + deltaX);
		} else {
			deltaX = 1;
			deltaY = 1;
			//System.out.println("deltaY: " + deltaY);
			//System.out.println("deltaX: " + deltaX);
		}
		
	}

	@Override
	public void mouseMoved(MouseEvent e) {	
	}
	
	private static void establishP2PCommunication(){
		////////P2P implementation - prompt user, establish connection//////////////
		BufferedReader in = new BufferedReader(new InputStreamReader(System.in));
		boolean newNetwork = false;
		String input = null;
		while (true) {
			System.out.println("Create a new network or join an existing network (internet connection required)?");
			System.out.println("  1. Create a new network");
			System.out.println("  2. Join an existing network");
			try {
				input = in.readLine();
			} catch (IOException e) {
				continue;
			}
			if (input.equals("1")) {
				newNetwork = true;
				break;
			} else if (input.equals("2"))
				break;
		}
		long id = -1;
		while (true) {
			System.out.println("Desired network ID, 0-" + (Peer.ID_LIMIT-1) + " [random]: ");
			try {
				input = in.readLine();
				if (input.length() == 0)
					break;
				id = Long.parseLong(input);
			} catch (NumberFormatException e) {
				continue;
			} catch (IOException e) {
				continue;
			}
			if (id >= -1 && id < Peer.ID_LIMIT)
				break;
		}
		peer = new Peer();
		if (newNetwork) {
			if (id == -1)
				peer.createNetwork();
			else
				peer.createNetwork(id);
		} else {
			String ip = "127.0.0.1";
			int port = Peer.DEFAULT_SERVER_PORT;
			do {
				System.out.print("Enter host to connect to [" + ip + "[:" + port + "]]: ");
				try {
					input = in.readLine();
					int index = input.indexOf(':');
					if (index >= 0) {
						port = Integer.parseInt(input.substring(index + 1));
						input = input.substring(0, index);
					}
					if (input.length() > 0)
						ip = input;
				} catch (IOException e) {
				}
			} while (id == -1 ? !peer.connectToNetwork(ip, port) : !peer.connectToNetwork(ip, port, id));
		}
	}
	
	private static void displayNetworkOptions(){
		
		/////provide user options for viewing network status or disconnecting////	
		System.out.println("Enter \"state\" to see network info or \"disconnect\" to disconnect.");
		BufferedReader in = new BufferedReader(new InputStreamReader(System.in));
		String input = null;
		while (true) {
			try {
				input = in.readLine();
				if (input != null && input.length() == 0)
					continue;
				Scanner sc = new Scanner(input);
				if (!sc.hasNextLong()) {
					input = sc.next();
					if (input.equals("disconnect")) {
						peer.disconnectFromNetwork();
						break;
					} else if (input.equals("state"))
						System.out.println(peer.internalState());
					else
						System.out.println("Enter \"state\" to see network info or \"disconnect\" to disconnect.");
					continue;
				}

			} catch (IOException e) {
			}
		}	
	}
}

